/*
 * Copyright (C) 2010 M. Hautle.
 * 
 * This file is part of SimpleEL.
 * 
 * SimpleEL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option) any later version.
 * 
 * SimpleEL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License along with SimpleEL. If not, see <http://www.gnu.org/licenses/>.
 */
package ch.simpleel;

import java.util.*;
import ch.simpleel.accessors.*;
import ch.simpleel.data.*;
import ch.simpleel.util.*;

/**
 * Speed test for {@link MiscUtils#getFirst(Collection, IValueAccessor, Object)}.
 * 
 * @author M. Hautle
 */
public class LookupSpeedTest {
    /** The number of test rounds. */
    private static final int ROUNDS = 10;

    /** The number of calls per round. */
    private static final int CNT = 1000000;

    private static final boolean LOOKUP = false;

    private static final boolean LOOKUP2 = true;

    private static final boolean NATIVE = true;

    /**
     * VM Args:<br>
     * 
     * <pre>
     * -server / -client
     * -Xms512m -Xmx512m
     * -XX:+PrintCompilation 
     * -verbose:gc
     * </pre>
     * 
     * @param args
     */
    public static void main(String[] args) {
        // ValueAccessorManager.setAccessProvider(new ReflectionAccessProvider());
        // ValueAccessorManager.setAccessProvider(new ASMFastAccessProvider());
        warmUp(20, CNT);
        System.gc();
        System.err.println("Real test");
        realTest(ROUNDS, CNT);
    }

    /**
     * Warmup for the JIT compiler.
     * 
     * @param rounds The number of rounds
     * @param cnt The number of calls per round
     */
    private static void warmUp(int rounds, int cnt) {
        long[] l = new long[rounds];
        long[] l2 = new long[rounds];
        long[] n = new long[rounds];
        runTest(rounds, cnt, l, l2, n);
        System.out.println("First call:");
        printCall(l, l2, n, 0);
        System.out.println("Second call:");
        printCall(l, l2, n, 1);
        System.out.println("Average:");
        System.out.println("Lookup: " + getAvg(l));
        System.out.println("Lookup2: " + getAvg(l2));
        System.out.println("Native: " + getAvg(n));
    }

    /**
     * Prints the specified call times.
     * 
     * @param l Array for timing values of {@link #lookup(List, String, TheDom)}
     * @param l2 Array for timing values of {@link #lookup2(List, IValueAccessor, String, TheDom)}
     * @param nativeTime Array for timing values of {@link #lookupNative(List, String, TheDom)}
     * @param i The index to print
     */
    private static void printCall(long[] l, long[] l2, long[] nativeTime, final int i) {
        System.out.println("Lookup: " + l[i]);
        System.out.println("Lookup2: " + l2[i]);
        System.out.println("Native: " + nativeTime[i]);
    }

    /**
     * Executes the real test.
     * 
     * @param rounds The number of rounds
     * @param cnt The number of calls
     */
    private static void realTest(int rounds, int cnt) {
        long[] l = new long[rounds];
        long[] l2 = new long[rounds];
        long[] n = new long[rounds];
        runTest(rounds, cnt, l, l2, n);
        System.out.println("Lookup: " + getAvg(l));
        System.out.println("Lookup2: " + getAvg(l2));
        System.out.println("Native: " + getAvg(n));
    }

    /**
     * Runs the test.
     * 
     * @param rounds The number of rounds
     * @param cnt The number of calls
     * @param l Array for timing values of {@link #lookup(List, String, TheDom)}
     * @param l2 Array for timing values of {@link #lookup2(List, IValueAccessor, String, TheDom)}
     * @param n Array for timing values of {@link #lookupNative(List, String, TheDom)}
     */
    private static void runTest(int rounds, int cnt, long[] l, long[] l2, long[] n) {
        final List<TheDom> list = new ArrayList<TheDom>();
        final TheDom exp = fill(list, "fooBar", 10, 5);
        for (int j = 0; j < rounds; j++)
            runTest(list, exp, l, l2, n, j, cnt);
    }

    /**
     * Runs the test code.
     * 
     * @param list The list to work with
     * @param exp The expected result entry
     * @param l Array for timing values of {@link #lookup(List, String, TheDom)}
     * @param l2 Array for timing values of {@link #lookup2(List, IValueAccessor, String, TheDom)}
     * @param n Array for timing values of {@link #lookupNative(List, String, TheDom)}
     * @param j The index for the current round
     * @param cnt The number of calls
     */
    private static void runTest(List<TheDom> list, TheDom exp, long[] l, long[] l2, long[] n, int j, final int cnt) {
        long t;
        if (LOOKUP) {
            t = System.currentTimeMillis();
            for (int i = 0; i < cnt; i++)
                lookup(list, "fooBar", exp);
            l[j] = System.currentTimeMillis() - t;
        }
        if (LOOKUP2) {
            final IValueAccessor<String> acc = ValueAccessorManager.getPropertyAccessor(TheDom.class, TheDom.ATTR_COUNTRY);
            t = System.currentTimeMillis();
            for (int i = 0; i < cnt; i++) {
                lookup2(list, acc, "fooBar", exp);
            }
            l2[j] = System.currentTimeMillis() - t;
        }
        if (NATIVE) {
            t = System.currentTimeMillis();
            for (int i = 0; i < cnt; i++)
                lookupNative(list, "fooBar", exp);
            n[j] = System.currentTimeMillis() - t;
        }
    }

    /**
     * Populates the given list.
     * 
     * @param res The list to fill
     * @param text The text for the special bean
     * @param cnt The number of entries
     * @param pos The position
     * @return The dom at the specified position
     */
    private static TheDom fill(List<TheDom> res, String text, int cnt, int pos) {
        final TheDom r = createTheDom();
        r.setCountry(text);
        for (int i = 0; i < pos; i++)
            res.add(createTheDom());
        res.add(r);
        for (int i = pos; i < cnt; i++)
            res.add(createTheDom());
        return r;
    }

    /**
     * Create and fill an test bean.
     * 
     * @return The dom
     */
    private static TheDom createTheDom() {
        final TheDom d = new TheDom();
        d.setCity("Bern");
        d.setCountry("Switzerland");
        d.setFirstname("Bob");
        d.setName("Baumeister");
        d.setState("-");
        d.setStreet("Eigerplatz");
        d.setStreetNumber("8");
        d.setZip("3000");
        return d;
    }

    /**
     * Lookup creating each time a {@link IValueAccessor}.
     * 
     * @param elements The element list
     * @param text The text to search
     * @param expected The expected result
     */
    private static void lookup(List<TheDom> elements, String text, TheDom expected) {
        TheDom res = MiscUtils.getFirst(elements, ValueAccessorManager.getPropertyAccessor(TheDom.class, TheDom.ATTR_COUNTRY), text);
        if (expected != res)
            throw new IllegalArgumentException();
    }

    /**
     * Native lookup.
     * 
     * @param elements The element list
     * @param text The text to search
     * @param expected The expected result
     */
    private static void lookupNative(List<TheDom> elements, String text, TheDom expected) {
        TheDom res = null;
        for (int i = 0, cnt = elements.size(); i < cnt; i++) {
            final TheDom b = elements.get(i);
            if (text.equals(b.getCountry())) {
                res = b;
                break;
            }
        }
        if (expected != res)
            throw new IllegalArgumentException();
    }

    /**
     * Lookup with passed accessor.
     * 
     * @param elements The element list
     * @param acc The accessor
     * @param text The text to search
     * @param expected The expected result
     */
    private static void lookup2(List<TheDom> elements, IValueAccessor<String> acc, String text, TheDom expected) {
        TheDom res = MiscUtils.getFirst(elements, acc, text);
        if (expected != res)
            throw new IllegalArgumentException();
    }

    /**
     * Computes the average time out of the passed ones.
     * 
     * @param values Timing values
     * @return The average
     */
    private static double getAvg(long[] values) {
        double sum = 0;
        for (long l : values)
            sum += l;
        return sum / values.length;
    }
}
